driver.js ➔ getOption   A
last analyzed

Complexity

Conditions 1
Paths 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 17
rs 9.55

1 Function

Rating   Name   Duplication   Size   Complexity  
A driver.js ➔ ... ➔ ??? 0 15 1
1
/**
2
 * WebExtension driver
3
 */
4
5
/** global: browser */
6
/** global: Wappalyzer */
7
8
const wappalyzer = new Wappalyzer();
9
10
var tabCache = {};
11
var categoryOrder = [];
12
var options = {};
13
var robotsTxtQueue = {};
14
15
browser.tabs.onRemoved.addListener(tabId => {
16
  tabCache[tabId] = null;
17
});
18
19
/**
20
 * Get a value from localStorage
21
 */
22
function getOption(name, defaultValue = null) {
23
  return new Promise((resolve, reject) => {
24
    const callback = item => {
25
      options[name] = item.hasOwnProperty(name) ? item[name] : defaultValue;
26
27
      resolve(options[name]);
28
    };
29
30
    browser.storage.local.get(name)
31
      .then(callback)
32
      .catch(error => {
33
        wappalyzer.log(error, 'driver', 'error')
34
35
        reject();
36
			});
37
  });
38
}
39
40
/**
41
 * Set a value in localStorage
42
 */
43
function setOption(name, value) {
44
  const option = {};
45
46
  option[name] = value;
47
48
  browser.storage.local.set(option);
49
50
  options[name] = value;
51
}
52
53
/**
54
 * Open a tab
55
 */
56
function openTab(args) {
57
  browser.tabs.create({
58
    url: args.url,
59
    active: args.background === undefined || !args.background
60
  });
61
}
62
63
/**
64
 * Make a POST request
65
 */
66
function post(url, body) {
67
  fetch(url, {
68
    method: 'POST',
69
    body: JSON.stringify(body)
70
  })
71
    .then(response => wappalyzer.log(`POST ${url}: ${response.status}`, 'driver'))
72
    .catch(error => wappalyzer.log(`POST ${url}: ${error}`, 'driver', 'error'));
73
}
74
75
fetch('../apps.json')
76
  .then(response => response.json())
77
  .then(json => {
78
    wappalyzer.apps = json.apps;
79
    wappalyzer.categories = json.categories;
80
81
    wappalyzer.parseJsPatterns();
82
83
    categoryOrder = Object.keys(wappalyzer.categories)
84
      .map(categoryId => parseInt(categoryId, 10))
85
      .sort((a, b) => wappalyzer.categories[a].priority - wappalyzer.categories[b].priority);
86
  })
87
  .catch(error => wappalyzer.log(`GET apps.json: ${error}`, 'driver', 'error'));
88
89
// Version check
90
let version = browser.runtime.getManifest().version;
91
92
getOption('version')
93
  .then(previousVersion => {
94
    if (previousVersion === null) {
95
      openTab({
96
        url: wappalyzer.config.websiteURL + 'installed'
97
      });
98
    } else if (version !== previousVersion) {
99
      getOption('upgradeMessage', true)
100
        .then(upgradeMessage => {
101
          if (upgradeMessage) {
102
            openTab({
103
              url: wappalyzer.config.websiteURL + 'upgraded?v' + version,
104
              background: true
105
            });
106
          }
107
        });
108
    }
109
110
    setOption('version', version);
111
  });
112
113
getOption('dynamicIcon', true);
114
getOption('pinnedCategory');
115
116
getOption('hostnameCache', {}).then(hostnameCache => wappalyzer.hostnameCache = hostnameCache);
117
118
// Run content script on all tabs
119
browser.tabs.query({ url: [ 'http://*/*', 'https://*/*' ] })
120
  .then(tabs => {
121
    tabs.forEach(tab => {
122
      browser.tabs.executeScript(tab.id, {
123
        file: '../js/content.js'
124
      });
125
    })
126
  })
127
  .catch(error => wappalyzer.log(error, 'driver', 'error'));
128
129
// Capture response headers
130
browser.webRequest.onCompleted.addListener(request => {
131
  const headers = {};
132
133
  if (request.responseHeaders) {
134
    const url = wappalyzer.parseUrl(request.url);
135
136
    browser.tabs.query({ url: [ url.href ] })
137
      .then(tabs => {
138
        const tab = tabs[0] || null;
139
140
        if (tab) {
141
          request.responseHeaders.forEach(header => {
142
            const name = header.name.toLowerCase();
143
144
            headers[name] = headers[name] || [];
145
146
            headers[name].push((header.value || header.binaryValue || '').toString());
147
          });
148
149
          if (headers['content-type'] && /\/x?html/.test(headers['content-type'][0])) {
150
            wappalyzer.analyze(url, { headers }, { tab });
151
          }
152
        }
153
      })
154
      .catch(error => wappalyzer.log(error, 'driver', 'error'));
155
  }
156
}, { urls: [ 'http://*/*', 'https://*/*' ], types: [ 'main_frame' ] }, [ 'responseHeaders' ]);
157
158
// Listen for messages
159
(chrome || browser).runtime.onMessage.addListener((message, sender, sendResponse) => {
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable chrome is declared in the current environment, consider using typeof chrome === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
160
  if (typeof message.id != 'undefined') {
161
    if (message.id !== 'log') {
162
      wappalyzer.log('Message' + (message.source ? ' from ' + message.source : '') + ': ' + message.id, 'driver');
163
    }
164
165
    let url = wappalyzer.parseUrl(sender.tab ? sender.tab.url : '');
166
    let response;
167
168
    switch ( message.id ) {
169
      case 'log':
170
        wappalyzer.log(message.subject, message.source);
171
172
        break;
173
      case 'init':
174
        browser.cookies.getAll({ domain: '.' + url.hostname })
175
          .then(cookies => wappalyzer.analyze(url, { cookies }, { tab: sender.tab }));
176
177
        break;
178
      case 'analyze':
179
        wappalyzer.analyze(url, message.subject, { tab: sender.tab });
180
181
				setOption('hostnameCache', wappalyzer.hostnameCache);
182
183
        break;
184
      case 'ad_log':
185
        wappalyzer.cacheDetectedAds(message.subject);
186
187
        break;
188
      case 'get_apps':
189
        response = {
190
          tabCache: tabCache[message.tab.id],
191
          apps: wappalyzer.apps,
192
          categories: wappalyzer.categories,
193
          pinnedCategory: options.pinnedCategory,
194
        };
195
196
        break;
197
      case 'set_option':
198
        setOption(message.key, message.value);
199
200
        break;
201
      case 'get_js_patterns':
202
        response = {
203
          patterns: wappalyzer.jsPatterns
204
        };
205
206
        break;
207
      default:
208
    }
209
210
    sendResponse(response);
211
  }
212
213
  return true;
214
});
215
216
wappalyzer.driver.document = document;
217
218
/**
219
 * Log messages to console
220
 */
221
wappalyzer.driver.log = (message, source, type) => {
222
  console.log(`[wappalyzer ${type}]`, `[${source}]`, message);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
223
};
224
225
/**
226
 * Display apps
227
 */
228
wappalyzer.driver.displayApps = (detected, meta, context) => {
229
  let tab = context.tab;
230
231
  if (tab === undefined) {
232
    return;
233
  }
234
235
  tabCache[tab.id] = tabCache[tab.id] || {
236
    detected: []
237
  };
238
239
  tabCache[tab.id].detected = detected;
240
241
  let found = false;
242
243
  // Find the main application to display
244
  [ options.pinnedCategory ].concat(categoryOrder).forEach(match => {
245
    Object.keys(detected).forEach(appName => {
246
      let app = detected[appName];
247
248
      app.props.cats.forEach(category => {
249
        if (category === match && !found) {
250
          let icon = app.props.icon || 'default.svg';
251
252
          if (!options.dynamicIcon) {
253
            icon = 'default.svg';
254
          }
255
256
          if (/\.svg$/i.test(icon)) {
257
            icon = 'converted/' + icon.replace(/\.svg$/, '.png');
258
          }
259
260
          try {
261
            browser.pageAction.setIcon({
262
              tabId: tab.id,
263
              path: '../images/icons/' + icon
264
            });
265
          } catch(e) {
266
            // Firefox for Android does not support setIcon see https://bugzilla.mozilla.org/show_bug.cgi?id=1331746
267
          }
268
269
          found = true;
270
        }
271
      });
272
    });
273
  });
274
275
  if (typeof chrome !== 'undefined') {
0 ignored issues
show
Bug introduced by
The variable chrome seems to not be initialized for all possible execution paths.
Loading history...
276
    // Browser polyfill doesn't seem to work here
277
    chrome.pageAction.show(tab.id);
278
  } else {
279
    browser.pageAction.show(tab.id);
280
  }
281
};
282
283
/**
284
 * Fetch and cache robots.txt for host
285
 */
286
wappalyzer.driver.getRobotsTxt = (host, secure = false) => {
287
  if (robotsTxtQueue.hasOwnProperty(host)) {
288
    return robotsTxtQueue[host];
289
  }
290
291
  robotsTxtQueue[host] = new Promise(resolve => {
292
    getOption('tracking', true)
293
      .then(tracking => {
294
        if (!tracking) {
295
          return resolve([]);
296
        }
297
298
        getOption('robotsTxtCache')
299
          .then(robotsTxtCache => {
300
            robotsTxtCache = robotsTxtCache || {};
301
302
            if ( host in robotsTxtCache ) {
303
              return resolve(robotsTxtCache[host]);
304
            }
305
306
            const timeout = setTimeout(() => resolve([]), 3000);
307
308
            fetch('http' + (secure ? 's' : '') + '://' + host + '/robots.txt', { redirect: 'follow' })
309
              .then(response => {
310
                clearTimeout(timeout);
311
312
                return response.ok ? response.text() : '';
313
              })
314
              .then(robotsTxt => {
315
                robotsTxtCache[host] = wappalyzer.parseRobotsTxt(robotsTxt);
316
317
                setOption('robotsTxtCache', robotsTxtCache);
318
319
                resolve(robotsTxtCache[host]);
320
              })
321
              .catch(() => resolve([]));
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
322
          });
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
323
      });
324
  })
325
  .finally(() => delete robotsTxtQueue[host]);
326
327
  return robotsTxtQueue[host];
328
};
329
330
/**
331
 * Anonymously track detected applications for research purposes
332
 */
333
wappalyzer.driver.ping = (hostnameCache = {}, adCache = []) => {
334
  getOption('tracking', true)
335
    .then(tracking => {
336
      if (tracking) {
337
        if (Object.keys(hostnameCache).length) {
338
          post('https://api.wappalyzer.com/ping/v1/', hostnameCache);
339
        }
340
341
        if (adCache.length) {
342
          post('https://ad.wappalyzer.com/log/wp/', adCache);
343
        }
344
345
        setOption('robotsTxtCache', {});
346
      }
347
    });
348
};
349